package cn.stimd.spring.aop.framework;

import cn.stimd.spring.aop.Advisor;
import cn.stimd.spring.aop.PointcutAdvisor;
import cn.stimd.spring.aop.TargetSource;
import cn.stimd.spring.aop.framework.adapter.AdvisorAdapterRegistry;
import cn.stimd.spring.aop.framework.adapter.DefaultAdvisorAdapterRegistry;
import cn.stimd.spring.aop.support.DefaultPointcutAdvisor;
import cn.stimd.spring.aop.target.SingletonTargetSource;
import org.aopalliance.aop.Advice;
import org.aopalliance.intercept.MethodInterceptor;
import org.springframework.util.ClassUtils;

import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 负责管理AOP代理的各项配置，比如对Advice和Advisor进行管理
 * 该类没有实现创建代理的方法，而是由子类来完成
 * <p>
 * Created by Stimd
 * 项目地址：https://gitee.com/stimd/spring-wheel
 * 公众号：Java编程探微
 */
public class AdvisedSupport extends ProxyConfig implements Advised{
    //目标对象
    TargetSource targetSource;
    //代理需要实现的接口
    private List<Class<?>> interfaces = new ArrayList<>();
    //与目标类相关的所有Advisor
    private List<Advisor> advisors = new LinkedList<>();
    //缓存了每个方法的拦截器链
    private transient Map<MethodCacheKey, List<Object>> methodCache = new ConcurrentHashMap<>(32);
    //注册适配器
    private static AdvisorAdapterRegistry registry = new DefaultAdvisorAdapterRegistry();


    //获取拦截器列表和动态拦截，最终由AopProxy的实现类在调用目标方法时使用
    //注：原本在DefaultAdvisorChainFactory中定义，只有一个方法，出于简化代码的考虑直接放在AdvisedSupport
    public List<Object> getInterceptorsAndDynamicInterceptionAdvice(Method method, Class<?> targetClass){
        //尝试从缓存中获取
        MethodCacheKey cacheKey = new MethodCacheKey(method);
        List<Object> cached = this.methodCache.get(cacheKey);

        if(cached == null){
            List<Object> interceptorList = new ArrayList<>(this.advisors.size());
            for (Advisor advisor : this.advisors) {
                //PointcutAdvisor
                if (advisor instanceof PointcutAdvisor) {
                    PointcutAdvisor pa = (PointcutAdvisor) advisor;

                    //检查ClassFilter和MethodMatcher是否符合条件
                    if (pa.getPointcut().getClassFilter().matches(targetClass)) {
                        MethodInterceptor[] interceptors = registry.getInterceptors(advisor);

                        if (pa.getPointcut().getMethodMatcher().matches(method, targetClass)) {
                            interceptorList.addAll(Arrays.asList(interceptors));
                        }
                    }
                }

                //IntroductionAdvisor略
            }
            cached = interceptorList;
            this.methodCache.put(cacheKey, cached);     //加入缓存
        }
        return cached;
    }


    @Override
    public void setTargetSource(TargetSource targetSource) {
        this.targetSource = targetSource;
    }

    @Override
    public TargetSource getTargetSource() {
        return this.targetSource;
    }

    @Override
    public Class<?> getTargetClass() {
        return this.targetSource.getTargetClass();
    }

    @Override
    public Class<?>[] getProxiedInterfaces() {
        return ClassUtils.toClassArray(this.interfaces);
    }

    @Override
    public Advisor[] getAdvisors() {
        return this.advisors.toArray(new Advisor[this.advisors.size()]);
    }

    @Override
    public void addAdvisor(Advisor advisor) {
        this.advisors.add(advisor);
    }

    /**
     * Advice被包装成DefaultPointcutAdvisor，允许代理任何类的任何方法
     */
    @Override
    public void addAdvice(Advice advice) {
        this.advisors.add(new DefaultPointcutAdvisor(advice));
    }


    /**
     * 设置被代理的目标对象
     */
    public void setTarget(Object target){
        setTargetSource(new SingletonTargetSource(target));
    }


    /**
     * 设置目标对象实现的接口
     */
    public void setInterfaces(Class<?>... interfaces){
        this.interfaces.clear();
        this.interfaces.addAll(Arrays.asList(interfaces));
    }


    public void addInterface(Class<?> intf) {
        if (!this.interfaces.contains(intf)) {
            this.interfaces.add(intf);
        }
    }


    private static final class MethodCacheKey implements Comparable<MethodCacheKey> {
        private final Method method;
        private final int hashCode;

        public MethodCacheKey(Method method) {
            this.method = method;
            this.hashCode = method.hashCode();
        }

        @Override
        public boolean equals(Object other) {
            return (this == other || (other instanceof MethodCacheKey &&
                    this.method == ((MethodCacheKey) other).method));
        }

        @Override
        public int hashCode() {
            return this.hashCode;
        }

        @Override
        public String toString() {
            return this.method.toString();
        }

        @Override
        public int compareTo(MethodCacheKey other) {
            int result = this.method.getName().compareTo(other.method.getName());
            if (result == 0) {
                result = this.method.toString().compareTo(other.method.toString());
            }
            return result;
        }
    }
}
